// Copyright 2004-2010 Castle Project - http://www.castleproject.org/
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Fas.Tem.Runtime.Parser
{
    using System;
    using System.IO;

    /// <summary>  NOTE : This class was originally an ASCII_CharStream autogenerated
    /// by Javacc.  It was then modified via changing class name with appropriate
    /// fixes for CTORS, and mods to readChar().
    ///
    /// This is safe because we *always* use Reader with this class, and never a
    /// InputStream.  This guarantees that we have a correct stream of 16-bit
    /// chars - all encoding transformations have been done elsewhere, so we
    /// believe that there is no risk in doing this.  Time will tell :)
    /// </summary>
    /// <summary> An implementation of interface CharStream, where the stream is assumed to
    /// contain only ASCII characters (without unicode processing).
    /// </summary>
    public sealed class VelocityCharStream : ICharStream
    {
        public const bool staticFlag = false;
        internal int bufferSize;
        internal int available;
        internal int tokenBegin;
        public int bufferPosition = -1;
        private int[] bufferLine;
        private int[] bufferColumn;

        private int column = 0;
        private int line = 1;

        private bool prevCharIsCR = false;
        private bool prevCharIsLF = false;

        private TextReader inputStream;

        private char[] buffer;
        private int maxNextCharInd = 0;
        private int inBuf = 0;
        private char currentCharacter;
        private bool currentCharacterAvailable = false;

        public VelocityCharStream(TextReader textReader,
                                  int startLine, int startColumn, int bufferSize)
        {
            inputStream = textReader;
            line = startLine;
            column = startColumn - 1;

            available = this.bufferSize = bufferSize;
            buffer = new char[bufferSize];
            bufferLine = new int[bufferSize];
            bufferColumn = new int[bufferSize];
        }

        public VelocityCharStream(TextReader textReader, int startLine, int startColumn)
            : this(textReader, startLine, startColumn, 4096)
        {
        }

        public void ReInit(TextReader textReader, int startLine, int startColumn, int bufferSize)
        {
            inputStream = textReader;
            line = startLine;
            column = startColumn - 1;

            if (buffer == null || bufferSize != buffer.Length)
            {
                available = this.bufferSize = bufferSize;
                buffer = new char[bufferSize];
                bufferLine = new int[bufferSize];
                bufferColumn = new int[bufferSize];
            }
            prevCharIsLF = prevCharIsCR = false;
            tokenBegin = inBuf = maxNextCharInd = 0;
            bufferPosition = -1;
        }

        public void ReInit(TextReader textReader, int startLine, int startColumn)
        {
            ReInit(textReader, startLine, startColumn, 4096);
        }

        public string GetImage()
        {
            if (bufferPosition >= tokenBegin)
            {
                int len = (bufferPosition - tokenBegin + 1) > buffer.Length ? buffer.Length : (bufferPosition - tokenBegin + 1);
                //return new String(buffer, tokenBegin, bufferPosition - tokenBegin + 1);
                return new string(buffer, tokenBegin, len);
            }
            else
            {
                return new string(buffer, tokenBegin, bufferSize - tokenBegin) + new string(buffer, 0, bufferPosition + 1);
            }
        }

        public char[] GetSuffix(int len)
        {
            char[] ret = new char[len];

            if ((bufferPosition + 1) >= len)
            {
                Array.Copy(buffer, bufferPosition - len + 1, ret, 0, len);
            }
            else
            {
                Array.Copy(buffer, bufferSize - (len - bufferPosition - 1), ret, 0, len - bufferPosition - 1);
                Array.Copy(buffer, 0, ret, len - bufferPosition - 1, bufferPosition + 1);
            }

            return ret;
        }

        public void Done()
        {
            buffer = null;
            bufferLine = null;
            bufferColumn = null;
        }

        /// <summary> Method to adjust line and column numbers for the start of a token.<br/>
        /// </summary>
        public void AdjustBeginLineColumn(int newLine, int newCol)
        {
            int start = tokenBegin;
            int len;

            if (bufferPosition >= tokenBegin)
            {
                len = bufferPosition - tokenBegin + inBuf + 1;
            }
            else
            {
                len = bufferSize - tokenBegin + bufferPosition + 1 + inBuf;
            }

            int i = 0;
            int j = 0;
            int k;
            int columnDiff = 0;

            while (i < len && bufferLine[j = start % bufferSize] == bufferLine[k = ++start % bufferSize])
            {
                bufferLine[j] = newLine;
                int nextColDiff = columnDiff + bufferColumn[k] - bufferColumn[j];
                bufferColumn[j] = newCol + columnDiff;
                columnDiff = nextColDiff;
                i++;
            }

            if (i < len)
            {
                bufferLine[j] = newLine++;
                bufferColumn[j] = newCol + columnDiff;

                while (i++ < len)
                {
                    if (bufferLine[j = start % bufferSize] != bufferLine[++start % bufferSize])
                    {
                        bufferLine[j] = newLine++;
                    }
                    else
                    {
                        bufferLine[j] = newLine;
                    }
                }
            }

            line = bufferLine[j];
            column = bufferColumn[j];
        }

        public int Column => bufferColumn[bufferPosition];

        public int Line => bufferLine[bufferPosition];

        public int EndColumn => bufferColumn[bufferPosition];

        public int EndLine => bufferLine[bufferPosition];

        public int BeginColumn => bufferColumn[tokenBegin];

        public int BeginLine => bufferLine[tokenBegin];

        public char CurrentCharacter
        {
            get
            {
                if (!currentCharacterAvailable)
                {
                    throw new InvalidOperationException("CurrentCharacter not available");
                }
                return currentCharacter;
            }
        }

        public bool CurrentCharacterAvailable => currentCharacterAvailable;


        private void ExpandBuff(bool wrapAround)
        {
            char[] newBuffer = new char[bufferSize + 2048];
            int[] newBufferLine = new int[bufferSize + 2048];
            int[] newBufferColumn = new int[bufferSize + 2048];

            try
            {
                if (wrapAround)
                {
                    Array.Copy(buffer, tokenBegin, newBuffer, 0, bufferSize - tokenBegin);
                    Array.Copy(buffer, 0, newBuffer, bufferSize - tokenBegin, bufferPosition);
                    buffer = newBuffer;

                    Array.Copy(bufferLine, tokenBegin, newBufferLine, 0, bufferSize - tokenBegin);
                    Array.Copy(bufferLine, 0, newBufferLine, bufferSize - tokenBegin, bufferPosition);
                    bufferLine = newBufferLine;

                    Array.Copy(bufferColumn, tokenBegin, newBufferColumn, 0, bufferSize - tokenBegin);
                    Array.Copy(bufferColumn, 0, newBufferColumn, bufferSize - tokenBegin, bufferPosition);
                    bufferColumn = newBufferColumn;

                    maxNextCharInd = (bufferPosition += (bufferSize - tokenBegin));
                }
                else
                {
                    Array.Copy(buffer, tokenBegin, newBuffer, 0, bufferSize - tokenBegin);
                    buffer = newBuffer;

                    Array.Copy(bufferLine, tokenBegin, newBufferLine, 0, bufferSize - tokenBegin);
                    bufferLine = newBufferLine;

                    Array.Copy(bufferColumn, tokenBegin, newBufferColumn, 0, bufferSize - tokenBegin);
                    bufferColumn = newBufferColumn;

                    maxNextCharInd = (bufferPosition -= tokenBegin);
                }
            }
            catch (Exception t)
            {
                throw new ApplicationException(t.Message);
            }

            bufferSize += 2048;
            available = bufferSize;
            tokenBegin = 0;
        }

        private bool FillBuff()
        {
            if (maxNextCharInd == available)
            {
                if (available == bufferSize)
                {
                    if (tokenBegin > 2048)
                    {
                        bufferPosition = maxNextCharInd = 0;
                        available = tokenBegin;
                    }
                    else if (tokenBegin < 0)
                    {
                        bufferPosition = maxNextCharInd = 0;
                    }
                    else
                    {
                        ExpandBuff(false);
                    }
                }
                else if (available > tokenBegin)
                {
                    available = bufferSize;
                }
                else if ((tokenBegin - available) < 2048)
                {
                    ExpandBuff(true);
                }
                else
                {
                    available = tokenBegin;
                }
            }

            try
            {
                int canRead = inputStream.Read(buffer, maxNextCharInd, available - maxNextCharInd);

                if (canRead <= 0)
                {
                    inputStream.Close();
                    return EndRead();
                }
                else
                {
                    maxNextCharInd += canRead;
                    return true;
                }
            }
            catch (Exception)
            {
                return EndRead();
            }
        }


        private bool EndRead()
        {
            --bufferPosition;
            Backup(0);
            if (tokenBegin == -1)
            {
                tokenBegin = bufferPosition;
            }
            return false;
        }


        public bool BeginToken()
        {
            tokenBegin = -1;
            bool success = ReadChar();
            if (success)
            {
                tokenBegin = bufferPosition;
            }

            return success;
        }

        private void UpdateLineColumn()
        {
            column++;

            if (prevCharIsLF)
            {
                prevCharIsLF = false;
                line += (column = 1);
            }
            else if (prevCharIsCR)
            {
                prevCharIsCR = false;
                if (currentCharacter == '\n')
                {
                    prevCharIsLF = true;
                }
                else
                {
                    line += (column = 1);
                }
            }

            switch (currentCharacter)
            {
                case '\r':
                    prevCharIsCR = true;
                    break;

                case '\n':
                    prevCharIsLF = true;
                    break;

                case '\t':
                    column--;
                    column += (8 - (column & 7));
                    break;

                default:
                    break;
            }

            bufferLine[bufferPosition] = line;
            bufferColumn[bufferPosition] = column;
        }


        public bool ReadChar()
        {
            if (inBuf > 0)
            {
                --inBuf;

                /*
				*  was : return (char)((char)0xff & buffer[(bufferPosition == bufferSize - 1) ? (bufferPosition = 0) : ++bufferPosition]);
				*/
                currentCharacterAvailable = true;
                currentCharacter = buffer[(bufferPosition == bufferSize - 1) ? (bufferPosition = 0) : ++bufferPosition];
                return true;
            }

            if (++bufferPosition >= maxNextCharInd)
            {
                if (!FillBuff())
                {
                    currentCharacterAvailable = false;
                    currentCharacter = default(char);
                    return false;
                }
            }

            /*
			*  was : char c = (char)((char)0xff & buffer[bufferPosition]);
			*/
            currentCharacterAvailable = true;
            currentCharacter = buffer[bufferPosition];

            UpdateLineColumn();
            return true;
        }

        public void Backup(int amount)
        {
            inBuf += amount;
            if ((bufferPosition -= amount) < 0)
            {
                bufferPosition += bufferSize;
            }
        }
    }
}